<!-- 代码已包含 CSS：使用 TailwindCSS , 安装 TailwindCSS 后方可看到布局样式效果 -->
<template>
    <div class="min-h-screen bg-slate-50">
        <!-- 不可点击的遮罩层 -->
        <div v-if="appInfo.enable==false" class="fixed inset-0 bg-gray-500 bg-opacity-50  z-40"></div>
        <!-- 左侧导航 -->
        <div class="fixed left-0 top-0 h-full  bg-white shadow-lg backdrop-blur-sm z-30 transition-all duration-300 "
             :class="{
      '-translate-x-full': (isSidebarHidden && isPC)||isSidebarHidden ,
      'w-64':isPC}
      ">
            <!-- 左侧导航 -->

            <!-- 搜索框 -->
            <div class="p-4">
                <div class="relative">
                    <input type="text" v-model="keywords" @keyup.enter="searchHistory" placeholder="" class="w-full pl-10 pr-4 py-2.5 bg-slate-50/50 rounded-xl text-sm border-none
                  focus:ring-2 focus:ring-blue-100/50 transition-all keywords-div">
                    <i class="fas fa-search absolute left-3 top-1/2 -translate-y-1/2 text-gray-400"></i>
                </div>
            </div>
            <!-- 对话历史列表 -->
            <div class="chat-list-div">
                <div class="overflow-y-auto h-[calc(100vh-80px)] ">
                    <div v-for="(chat,index)  in chats" :key="chat.id" :class="{'bg-blue-50': chat.id === activeChatId}"
                         @click="activeChatId = chat.id" @scroll="handleSessionScroll"
                         @mouseenter="showDeleteButton(index)" @mouseleave="handleMouseLeave($event, index)"
                         class="group px-4 py-4 hover:bg-slate-50 cursor-pointer transition-colors border-b border-slate-100 last:border-b-0">
                        <div class="flex items-start justify-between gap-1">
                            <div class="flex flex-col min-w-0">
                                <span class="text-sm text-gray-700 font-medium mb-1 truncate">{{ chat.name }}</span>
                                <span class="text-xs text-gray-400">{{ chat.createTime }}</span>
                            </div>
                            <span :class="{ 'hidden':!showDelete[index] }" v-if="isPC">
								<jb-btn icon="" text   type="error" circle :confirmText="'确认删除该会话？'"
                                        :url="`/aiAppSession/delete/${chat.id}`" @success="handleDeleteChat(index)"
                                        @open="openConfirm(index)" @cancel="handleCancel(index)"
                                        @close="closeConfirm(index)">
                                    <jb-icon :local="true" icon="ri:delete-bin-line"></jb-icon>
                                </jb-btn>
							</span>
                            <span v-else>
								<jb-btn :icon="Icons.DELETE" text type="error" circle :confirmText="'确认删除该会话？'"
                                        color="#c3c3c3" :url="`/aiAppSession/delete/${chat.id}`"
                                        @success="handleDeleteChat(index)"></jb-btn>
							</span>

                        </div>
                    </div>
                </div>
            </div>
            <div class="absolute -right-10 top-1 md:top-4">
                <button @click="toggleSidebar"
                        class="!rounded-button w-8 h-8 flex items-center justify-center text-gray-900 hover:text-gray-600 bg-white transition-all">
                    <i class="fas" :class="isSidebarHidden? 'fa-chevron-right' : 'fa-chevron-left'"></i>
                </button>
            </div>
        </div>
        <!-- 主聊天区域 -->
        <div class=" h-screen flex flex-col bg-slate-50/50 transition-all duration-300" :class="{
       'ml-64':!isSidebarHidden && isPC,
       'ml-0': isSidebarHidden ||!isPC,
       'w-[calc(100%-16rem)]':!isSidebarHidden && isPC, // 当侧边栏显示时，预留侧边栏宽度
       'w-full': isSidebarHidden ||!isPC // 当侧边栏隐藏或屏幕小于1024px时，全宽
     }">
            <!-- 遮罩层-->
            <div v-if="!isSidebarHidden && !isPC" class="fixed  inset-0 bg-gray-500 bg-opacity-50  z-10"
                 @click="toggleSidebar"></div>
            <!-- 顶部栏 -->
            <div
                class="min-h-28 md:min-h-32 pt-8 pb-6 md:pt-4 md:py-4 border-b bg-white backdrop-blur-sm flex items-center justify-between px-4 md:px-16 relative shadow-sm">
                <div class="absolute top-1 md:top-4 right-4 flex gap-2">
                    <button @click="handleAddChat"
                            class="!rounded-button w-8 h-8 flex items-center justify-center text-slate-500 hover:text-slate-700 bg-slate-100 hover:bg-slate-200 transition-all">
                        <n-popover trigger="hover">
                            <template #trigger>
                                <i class="fas fa-plus text-sm"></i>
                            </template>
                            <span>添加新会话</span>
                        </n-popover>
                    </button>

                    <n-popconfirm @positive-click="handlePositiveClick" @negative-click="handleNegativeClick">
                        <template #trigger>
                            <button
                                class="!rounded-button w-8 h-8 flex items-center justify-center text-gray-500 hover:text-gray-700 bg-slate-100 hover:bg-gray-200 transition-all">
                                <jb-icon :local="true" icon="ri:delete-bin-line"></jb-icon>
                            </button>
                        </template>
                        确定要清空当前聊天记录么?
                    </n-popconfirm>


                </div>
                <div class="max-w-full flex-1 flex flex-col md:flex-row items-center justify-center gap-3 md:gap-6">
                    <div class="w-12 h-12 md:w-16 md:h-16 rounded-2xl flex items-center justify-center shrink-0">
                        <img :src="appInfo.appLogo" alt="Logo" class="w-full h-full object-contain">
                    </div>
                    <div class="flex flex-col items-center md:items-start  overflow-hidden max-w-full">
                        <h2
                            class="text-2xl md:text-3xl font-bold bg-gradient-to-r from-[#4f46e5] via-[#4338ca] to-[#3730a3] bg-clip-text text-transparent tracking-wider text-center md:text-left whitespace-nowrap text-ellipsis overflow-hidden max-w-full">
                            {{ appInfo.name }}
                        </h2>
                        <div class="flex items-center gap-4 md:mt-2 max-w-full">
                            <p
                                class="text-gray-500 font-medium whitespace-nowrap text-ellipsis overflow-hidden max-w-full">
                                {{ appInfo.briefInfo }}
                            </p>
                        </div>
                    </div>
                </div>
            </div>
            <!-- 对话内容区 -->
            <div v-if="!activeChatId"
                 style="display: flex; justify-content: center;align-items: center;flex:1;font-size: 36px;font-weight: 500;color:#d5d5d5">
                请先创建或选择一个会话
            </div>
            <div v-show="activeChatId" class="chat_panel" style="position: relative" ref="chatContainer"></div>
        </div>
    </div>
</template>
<script lang="ts" setup>
import {JBoltApi} from "@/service/request/index.js";
import {useMessage} from 'naive-ui'
import {
    ref,
    onMounted,
    onBeforeMount,
    reactive,
    watch,
    onActivated,
    onDeactivated
} from 'vue';
import {
    Icons
} from '@/constants'
import {
    useRoute
} from "vue-router";
import {
    useWebSocketStore
} from "@/stores/websocket.js";
import {storeToRefs} from "pinia";
import markdownit from 'markdown-it'
import AiChat from '@/components/_builtin/jb-ai-chat/index.js'
import katex from "katex";


const route = useRoute()
const appSn = route.params.appSn;
const appId = ref(null)
const chats = ref([]);
const activeChatId = ref(null);
const message = useMessage()
const showDelete = ref({})// 控制删除按钮显示的状态
const confirmOpen = ref({}) // 控制询问框是否打开的状态
const currentChat = ref({
    id: '',
    name: '',
    hasMsg: false,
    config: {
        llmConfig: {
            systemRole: ''
        }
    }
})

//默认当前的app对象
const appInfo = ref({
    name: '',
    userAvatar: '',
    showAvatar: false,
    assistantAvatar: '',
    appLogo: '',
    briefInfo: '',
    enable: false
})


const isSidebarHidden = ref(false);
const isPC = ref(true);
const window = ref(globalThis.window);
// 监听窗口大小变化
const handleResize = () => {
    isPC.value = window.value.innerWidth >= 1024;
    if (!isPC.value) {
        isSidebarHidden.value = true;
    }
};
const webSocketStore = storeToRefs(useWebSocketStore())
const chatContainer = ref(null);
const chatInstance = ref(null);
const chatState = ref({
    pageSize: 20,
    hasMore: true,
})

const md = markdownit({
    html: true,
    breaks: true,
    linkify: true,
    typographer: true
});
md.use(texmath, {
    engine: katex,
    delimiters: [
        "dollars", "brackets"
    ]
})
const showDeleteButton = (index) => {
    if (!confirmOpen.value[index]) {
        showDelete.value[index] = true;
    }
};

const handleMouseLeave = (event, index) => {
    const target = event.relatedTarget;
    const isMovingToPopover = target && (target.classList.contains('n-popover') || target.classList.contains('n-popover-arrow'));
    if (target && !event.currentTarget.contains(target) && !confirmOpen.value[index] && !isMovingToPopover) {
        showDelete.value[index] = false;
    }
};


const openConfirm = (index) => {
    confirmOpen.value[index] = true;
};

const closeConfirm = (index) => {
    confirmOpen.value[index] = false;
    showDelete.value[index] = false;
};
const handleCancel = (index) => {
    // 隐藏删除按钮
    showDelete.value[index] = false;
    // 重置确认框打开状态
    confirmOpen.value[index] = false;
};


function handlePositiveClick() {
    JBoltApi.tryGet(`/aiAppMessage/deleteBySessionId/${activeChatId.value}`).then(res => {
        message.success('聊天记录清除成功');
        loadChatInstance()
    }).catch((err) => {
        console.error(err);
    })
}

function handleNegativeClick() {
    message.info('已取消')
}

// const _chatContainer = ref<HTMLElement | null>(null);
const keywords = ref('');

watch(() => webSocketStore.ws.value, () => {
    console.debug("websock重连")
    if (chatInstance.value) {
        chatInstance.value.updateWebSocket(webSocketStore.ws.value)
    }
})

watch(() => chatContainer.value, (newV) => {
    console.debug("chatContainer watch...")
    //dom初始化好了
    if (newV && activeChatId.value && !chatInstance.value) {
        loadChatInstance()
    }
})
watch(() => activeChatId.value, (newV) => {
    if (newV) {
        if (chatInstance.value) {
            chatInstance.value.destroy();
        }
        JBoltApi.tryGet(`/aiAppSession/getById/${activeChatId.value}`).then(res => {
            currentChat.value = {
                ...res.data,
                hasMsg: false
            }
        }).catch((error) => {
            console.error(error);
        })
        if (chatContainer.value) {
            chatState.value = {
                pageSize: 20,
                hasMore: true
            }
            loadChatInstance()
        }
        if (!isPC.value) {
            isSidebarHidden.value = true;
        }
    }

})

function loadChatInstance() {
    //初始化aichat
    chatInstance.value = new AiChat(chatContainer.value, {
        websocketInstance: webSocketStore.ws.value,
        app: appId.value,
        sessionId: activeChatId.value,
        global: null,
        showAvatar: appInfo.value.showAvatar ?? true,
        userAvatar: appInfo.value.userAvatar ?? "",
        assistantAvatar: appInfo.value.assistantAvatar ?? "",
        onMsgSend: (msg) => {
            if (currentChat.value.hasMsg == false) {
                handleUpdateSessionName(currentChat.value.id, msg.data.content.text);
                currentChat.value.hasMsg = true;
            }
        },
        onMsgToDelete: async (id) => {
            console.debug("删除消息", id)
            let {
                result
            } = await JBoltApi.get(`/aiAppMessage/delMsgById/${id}`)
            if (result.code == 0) {
                message.success("删除成功");
                return true;
            } else {
                message.error("删除失败");
                return false;
            }
        },
        onMarkdownRender: (markdown) => {
            return md.render(markdown);
        },
        onHistoryLoad: async (startId, global) => {
            if (chatState.value.hasMore == false) return;
            let {
                result
            } = await JBoltApi.get('/aiAppMessage/getListByPage', {
                sessionId: activeChatId.value,
                startId,
                pageSize: chatState.value.pageSize,
            })

            let {
                list,
                totalPage
            } = result.data
            chatState.value.hasMore = chatState.value.pageSize == list?.length;
            // console.error("加载历史记录开始", list)
            if (!list || !list.length) {
                // console.error(currentChat.value.hasMsg)
                return false;
            }
            // messages.value = [...messages.value, ...list]
            // console.debug("加载历史记录完成...", list)
            currentChat.value.hasMsg = true;
            return list.map(item => {
                return {
                    id: item.id,
                    flag: item.role == "user" ? item.id : item.linkId,
                    data: {
                        role: item.role,
                        format: item.role == 'assistant' ? "markdown" : "",
                        content: {
                            text: item.content,
                        },
                        reference: item.reference,
                        errorMsg: item.errorMsg,
                    },
                    createTime: item.createTime
                }
            })
        }
    })
}

// 处理滚动事件
function handleSessionScroll(e) {
    const {
        scrollHeight,
        scrollTop,
        clientHeight
    } = e.target;
    // 距离底部100px时加载更多
    if (scrollHeight - scrollTop - clientHeight < 100 && !chatPageState.loading && chatPageState.hasMore) {
        loadChatList(false);
    }
}

//更新session
function handleUpdateSessionName(sessionId, msg) {
    var name = msg.substring(0, 20);
    JBoltApi.post('/aiAppSession/update', {
        name: name,
        id: sessionId,
        appId: appId.value
    }).then(() => {
        const itemToUpdate = chats.value.find(item => item.id === sessionId);
        if (itemToUpdate) {
            itemToUpdate.name = name;
        }
    });
}

//获取app
function getApp() {
    JBoltApi.tryGet(`/aiApp/getBySn/${appSn}`).then((res) => {

        appInfo.value = res.data;
        appId.value = appInfo.value.id;
        const uiConfig = res.data.config?.uiConfig;
        if (uiConfig) {
            appInfo.value.showAvatar = !!uiConfig.showAvatar;
            console.debug(uiConfig)
            //如果没有传logo,就用默认JBoltAI头像
            if (uiConfig.appLogo != undefined && uiConfig.appLogo != '') {
                appInfo.value.appLogo = uiConfig.appLogo;
            } else {
                appInfo.value.appLogo = 'https://static.jbolt.cn/website/jboltai_logo_icon.png';
            }
            appInfo.value.assistantAvatar = uiConfig.assistantAvatar;
            appInfo.value.userAvatar = uiConfig.userAvatar;
        } else {
            appInfo.value.appLogo = 'https://static.jbolt.cn/website/jboltai_logo_icon.png';
        }

        if (!appInfo.value.enable) { //应用已发布
            message.error("应用未发布，请先发布应用");
        }
        // 页面加载时设置标题
        document.title = appInfo.value.name;
        loadChatList(true);
    });
}


//重新刷新session列表
function resetChatList() {
    chatPageState.startId = null;
    chatPageState.hasMore = true;
    chatPageState.loading = false;
    chats.value = [];
}

//新建session
function handleAddChat() {
    JBoltApi.post('/aiAppSession/add', {
        name: '新的会话',
        appId: appId.value, //内置appId
    }).then(() => {
        resetChatList();
        loadChatList(true);
    });
}

//关键词搜索session
const searchHistory = () => {
    resetChatList()
    loadChatList(true);
};

const toggleSidebar = () => {
    isSidebarHidden.value = !isSidebarHidden.value;
};


const chatPageState = reactive({
    pageSize: 20,
    startId: null,
    hasMore: true,
    loading: false,
});

//删除session对象
function handleDeleteChat(index) {
    if (activeChatId.value == chats.value[index].id) {
        activeChatId.value = null;
    }
    chats.value.splice(index, 1);
    // 隐藏删除按钮
    showDelete.value[index] = false;
    // 重置确认框打开状态
    confirmOpen.value[index] = false;
}

//加载session列表
async function loadChatList(isFirst) {
    // console.error("调用")
    if (chatPageState.loading || chatPageState.hasMore == false) return;
    // console.error("调用22")
    chatPageState.loading = true;
    try {
        const {
            result
        } = await JBoltApi.get('/aiAppSession/getListByPage', {
            pageSize: chatPageState.pageSize,
            startId: chatPageState.startId,
            keywords: keywords.value,
            appId: appId.value, //内置应用
        });
        let {
            list,
            totalPage
        } = result.data;

        list = list ?? [];
        chatPageState.hasMore = list.length == chatPageState.pageSize;

        chatPageState.startId = list[list.length - 1]?.id;
        chats.value.push(...list)

        if (isFirst) {
            if (chats.value.length == 0) {
                handleAddChat();
            } else {
                activeChatId.value = chats.value[0]?.id
            }

        }
    } catch (error) {
        console.error('加载聊天列表失败:', error);
    } finally {
        chatPageState.loading = false;
    }
}

onMounted(() => {
    getApp();
    // scrollToBottom();



});
onBeforeMount(() => {
    // 初始化时检查窗口大小
    handleResize();
    // 添加窗口大小变化监听器
    window.value.addEventListener('resize', handleResize);
    if (chatInstance.value) {
        chatInstance.value.destroy()
    }
});
onActivated(() => {
    if (!chatInstance.value && activeChatId.value) {
        chatState.value.hasMore = true
        loadChatInstance()
    }
    console.error("aiChat组件被激活,当前被选中的id:" + activeChatId.value)
})
onDeactivated(() => {
    if (chatInstance.value) {
        chatInstance.value.destroy()
        chatInstance.value = null;
    }
})
</script>
<style scoped>
.chat-container::-webkit-scrollbar {
    width: 6px;
}

.chat-container::-webkit-scrollbar-track {
    background: transparent;
}

.chat-container::-webkit-scrollbar-thumb {
    background-color: rgba(156, 163, 175, 0.5);
    border-radius: 3px;
}

input[type="range"] {
    -webkit-appearance: none;
    width: 100%;
    height: 4px;
    border-radius: 2px;
    background: #e5e7eb;
    outline: none;
}

input[type="range"]::-webkit-slider-thumb {
    -webkit-appearance: none;
    appearance: none;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    background: #4f46e5;
    cursor: pointer;
    transition: background .2s ease-in-out;
}

input[type="range"]::-webkit-slider-thumb:hover {
    background: #4338ca;
}

textarea:focus {
    outline: none;
    box-shadow: 0 0 0 2px rgba(79, 70, 229, 0.1);
}

[data-v-8aed1f8e] {
    width: 100%;
    margin: 0 auto;
}

@keyframes fadeIn {
    from {
        opacity: 0;
        transform: translateY(10px);
    }

    to {
        opacity: 1;
        transform: translateY(0);
    }
}

@keyframes fadeOut {
    from {
        opacity: 1;
        transform: translateY(0);
    }

    to {
        opacity: 0;
        transform: translateY(10px);
    }
}

.animate-fade-in {
    animation: fadeIn 0.3s ease-out forwards;
}

.animate-fade-out {
    animation: fadeOut 0.3s ease-out forwards;
}

.chat_panel {
    flex: 1;
    overflow: hidden;
}


.keywords-div {
    outline: none;
    /* 移除默认的outline */
    box-shadow: 0 2px 10px 3px rgb(0 0 0 / 5%), inset 0 1px 2px rgba(255, 255, 255, 0.1);;
    /* 自定义聚焦时的效果 */
}

.keywords-div:focus {
    /* 移除默认的outline */
    box-shadow: 0 2px 10px 3px rgba(0, 0, 0, 0.2), inset 0 1px 2px rgba(255, 255, 255, 0.1);;
    /* 自定义聚焦时的效果 */
}
</style>